/////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2009 Eric Bowen
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
// associated documentation files (the "Software"), to deal in the Software without restriction, including 
// without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
// copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 
// following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial 
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 
// LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
/////////////////////////////////////////////////////////////////////////////////////////////////////
// Author's blog: http://blog.scrappydog.com
// Updated source code available:  http://scrappydb.codeplex.com
/////////////////////////////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.ObjectModel;
using System.Web.Caching;
using Amazon.SimpleDB.Model;
using ScrappyDB.Attributes;
using ScrappyDB.Utilities;

namespace ScrappyDB
{
    public partial class Db
    {
        #region "Basic Count overloads with ConsistentRead defaulting to FALSE"

        public int Count<T>() where T : class, new()
        {
            return Count<T>(null, false);
        }

        public int Count<T>(string query) where T : class, new()
        {
            return Count<T>(query, null, 0, 100, false);
        }

        public int Count<T>(string query, string nextToken) where T : class, new()
        {
            return Count<T>(query, nextToken, 0, 100, false);
        }

        public int Count<T>(string query, string nextToken, int startPosition, int rowsToReturn)
            where T : class, new()
        {
            return Count<T>(query, nextToken, startPosition, rowsToReturn, false);
        }

        #endregion

        #region "Count overloads with ConsistentRead parameter"

        public int Count<T>(bool consistentRead) where T : class, new()
        {
            return Count<T>(null, consistentRead);
        }

        public int Count<T>(string query, bool consistentRead) where T : class, new()
        {
            return Count<T>(query, null, 0, 100, consistentRead);
        }

        public int Count<T>(string query, string nextToken, bool consistentRead) where T : class, new()
        {
            return Count<T>(query, nextToken, 0, 100, consistentRead);
        }

        #endregion

        #region IDb Members

        public int Count<T>(string query, string nextToken, int startPosition, int rowsToReturn, bool consistentRead)
            where T : class, new()
        {
            //TODO: implement paging - using count to find the start position... etc...
            Log.Debug("DB.Count Start: " + DateTime.Now.ToLongTimeString());

            try
            {
                //caching will break consistent reads so we need to disable it
                if (consistentRead)
                    WebCache = null;

                var o = new T();
                string domainName = EntityHelper.GetDomainName(o);
                int result = 999999;
                var request = new SelectRequest
                {
                    ConsistentRead = consistentRead
                };

                if (!string.IsNullOrEmpty(nextToken))
                    request.NextToken = nextToken;

                request.SelectExpression = "select count(*) from " + domainName + " ";

                string entityName = null;

                if (o.GetType().GetCustomAttributes(typeof(NoEntityNameFieldAttribute), true).Length == 0)
                {
                    entityName = EntityHelper.GetEntityName(o);
                    if (!string.IsNullOrEmpty(entityName))
                        request.SelectExpression += " where EntityName = '" + entityName + "' ";
                }

                if (!string.IsNullOrEmpty(query) && string.IsNullOrEmpty(entityName))
                    request.SelectExpression += " where " + query;
                else
                {
                    if (!string.IsNullOrEmpty(query))
                        request.SelectExpression += " and " + query;
                }

                if (rowsToReturn > 0 && rowsToReturn != 100 && rowsToReturn <= 2500)
                    request.SelectExpression += " limit " + rowsToReturn;

                string cacheKey = Utility.GetMd5Sum(request.SelectExpression);
                if (WebCache != null)
                {
                    //this "should" be unique...
                    object cacheResult = WebCache.Get(cacheKey);
                    if (cacheResult != null)
                    {
                        var c = (int)cacheResult;
                        Log.Info("CACHE HIT: ScrappyDB.Count<T>: Domain: {0} - Entity: {1} ", domainName,
                                 EntityHelper.GetEntityName(o));
                        Log.Info("CACHE HIT: " + request.SelectExpression);

                        return c;
                    }
                }

                Log.Info("ScrappyDB.Count<T>: Domain: {0} - Entity: {1} ", domainName,
                         EntityHelper.GetEntityName(o));
                Log.Info(request.SelectExpression);

                SelectResponse response = Service.Select(request);

                if (response.IsSetResponseMetadata())
                    Utility.TraceResponse(response.ResponseMetadata, response.ToXML());

                //TODO: need to deal with counts that take longer than 5 seconds to run
                if (response.SelectResult.IsSetNextToken())
                    Log.Warn(
                        "Count took longer than 5 seconds to run or exceeded rowsToReturn limit, incomplete result returned...");
                //    result.NextToken = response.SelectResult.NextToken;

                if (response.IsSetSelectResult())
                {
                    SelectResult selectResult = response.SelectResult;

                    result = int.Parse(selectResult.Item[0].Attribute[0].Value);

                    if (WebCache != null)
                    {
                        WebCache.Add(cacheKey, result, null, Configuration.CacheTimeout, Cache.NoSlidingExpiration,
                                     CacheItemPriority.Default, null);
                        Log.Info("CACHE ADD: key - {0}", cacheKey);
                    }
                }

                Log.Info("Count result: {0}", result);

                return result;
            }
            catch (Exception ex)
            {
                Log.Error("EXCEPTION: " + ex.Message);
                Log.Error(ex.StackTrace);
                throw;
            }
        }

     
        public Collection<T> CountByExample<T>(T exampleEntity) where T : class, new()
        {
            throw new NotImplementedException("CountByExample<T>");
        }

        #endregion

        public int CountRawSql(string query, string nextToken, bool consistentRead)
        {
            //TODO: implement paging - using count to find the start position... etc...
            Log.Debug("DB.Count Start: " + DateTime.Now.ToLongTimeString());

            try
            {
                //caching will break consistent reads so we need to disable it
                if (consistentRead)
                    WebCache = null;

                int result = 999999;
                var request = new SelectRequest
                                  {
                                      ConsistentRead = consistentRead
                                  };

                if (!string.IsNullOrEmpty(nextToken))
                    request.NextToken = nextToken;

                request.SelectExpression = query;

                string cacheKey = Utility.GetMd5Sum(request.SelectExpression);
                if (WebCache != null)
                {
                    //this "should" be unique...
                    object cacheResult = WebCache.Get(cacheKey);
                    if (cacheResult != null)
                    {
                        Log.Info("CACHE HIT: ScrappyDB.CountRawSql: SQL: {0} ", query);
                        Log.Info("CACHE HIT: " + request.SelectExpression);

                        return int.Parse(cacheResult.ToString());
                    }
                }

                Log.Info("ScrappyDB.CountRawSql: SQL: {0} ", query);
                Log.Info(request.SelectExpression);

                SelectResponse response = Service.Select(request);

                if (response.IsSetResponseMetadata())
                    Utility.TraceResponse(response.ResponseMetadata, response.ToXML());

                //TODO: need to deal with counts that take longer than 5 seconds to run
                if (response.SelectResult.IsSetNextToken())
                {
                    Log.Warn(
                        "Count took longer than 5 seconds to run or exceeded rowsToReturn limit, incomplete result returned...");
                    //    result.NextToken = response.SelectResult.NextToken;
                    //TODO: Add recursive call to get the rest of the count
                }

                if (response.IsSetSelectResult())
                {
                    SelectResult selectResult = response.SelectResult;

                    result = int.Parse(selectResult.Item[0].Attribute[0].Value);

                    if (WebCache != null)
                    {
                        WebCache.Add(cacheKey, result, null, Configuration.CacheTimeout, Cache.NoSlidingExpiration,
                                     CacheItemPriority.Default, null);
                        Log.Info("CACHE ADD: key - {0}", cacheKey);
                    }
                }

                Log.Info("Count result: {0}", result);

                return result;
            }
            catch (Exception ex)
            {
                Log.Error("EXCEPTION: " + ex.Message);
                Log.Error(ex.StackTrace);
                throw;
            }
        }
    }
}